var md5 = require('../lib/md5');

/*
 * url endpoints for different regions
 * Endpoint与Region的对应关系(官方文档): https://docs.ksyun.com/documents/6761
 */
const ENDPOINT = {
    BEIJING : 'ks3-cn-beijing.ksyuncs.com',
    SHANGHAI: 'ks3-cn-shanghai.ksyuncs.com',
    GUANGZHOU: 'ks3-cn-guangzhou.ksyuncs.com',
    HONGKONG: 'ks3-cn-hk-1.ksyuncs.com',
    RUSSIA: 'ks3-rus.ksyuncs.com',
    SINGAPORE: 'ks3-sgp.ksyuncs.com',
    JR_BEIJING: 'ks3-jr-beijing.ksyuncs.com',
    JR_SHANGHAI: 'ks3-jr-shanghai.ksyuncs.com',
    GOV_BEIJING: 'ks3-gov-beijing.ksyuncs.com'
};

/**
 * 给url添加请求参数
 * @param url
 * @param obj
 * @returns {string}  带请求参数的url
 */
function addURLParam(url, obj){
    url += url.indexOf("?") == -1  ? "?" : "";

    var ret = [];
    for(var key in obj){
        key = encodeURIComponent(key);
        var value = obj[key];
        if(value && Object.prototype.toString.call(value) == '[object String]'){
            ret.push(key + '=' + encodeURIComponent(value));
        }
    }
    return url + ret.join('&');
};

/**
 * key 进行encodeURIComponent编码，'/'不能被编码
 * 
 */
 function encodeKey(key) {
	var newKey = encodeURIComponent(key);
	newKey = newKey.replace(/%2F/g, '/');
	if (newKey.startsWith('/')) newKey = newKey.replace('/', '%2F')
	// '//'needs convert to '/%2F'
	if (newKey.includes('//')) newKey = newKey.replace(/\/\//g, '/%2F')
	return newKey
}

/*基于Javascript的Base64加解密算法*/
const Base64 = {
    encTable :[  /*Base64编码表*/
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
        'I', 'J', 'K', 'L', 'M', 'N', 'O' ,'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
        'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
        'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
        'w', 'x', 'y', 'z', '0', '1', '2', '3',
        '4', '5', '6', '7', '8', '9', '+', '/'
    ],
    // decTable:[ /*Base64解码表*/
    //     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    //     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    //     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    //     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    //     -1, -1, -1, 62, -1, -1, -1, 63, 52, 53,
    //     54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
    //     -1, -1, -1, -1, -1, 00, 01, 02, 03, 04,
    //     05, 06, 07, 08, 09, 10, 11, 12, 13, 14,
    //     15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
    //     25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
    //     29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
    //     39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
    //     49, 50, 51, -1, -1, -1, -1, -1
    // ],
    encUTF8: function(str) { /*将任意字符串按UTF8编码*/
        var code, res =[], len =str.length;
        var byte1, byte2, byte3, byte4, byte5, byte6;
        for (var i = 0; i < len; i++) {
            //Unicode码：按范围确定字节数
            code = str.charCodeAt(i);

            //单字节ascii字符：U+00000000 – U+0000007F	0xxxxxxx
            if (code > 0x0000 && code <= 0x007F) res.push(code);

            //双字节字符：U+00000080 – U+000007FF	110xxxxx 10xxxxxx
            else if (code >= 0x0080 && code <= 0x07FF) {
                byte1 = 0xC0 | ((code >> 6) & 0x1F);
                byte2 = 0x80 | (code & 0x3F);
                res.push(byte1, byte2);
            }

            //三字节字符：U+00000800 – U+0000FFFF	1110xxxx 10xxxxxx 10xxxxxx
            else if (code >= 0x0800 && code <= 0xFFFF) {
                byte1 = 0xE0 | ((code >> 12) & 0x0F);
                byte2 = 0x80 | ((code >> 6) & 0x3F);
                byte3 = 0x80 | (code & 0x3F);
                res.push(byte1, byte2, byte3);
            }

            //四字节字符：U+00010000 – U+001FFFFF	11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
            else if (code >= 0x00010000 && code <= 0x001FFFFF) {
                byte1 =0xF0 | ((code>>18) & 0x07);
                byte2 =0x80 | ((code>>12) & 0x3F);
                byte3 =0x80 | ((code>>6) & 0x3F);
                byte4 =0x80 | (code & 0x3F);
                res.push(byte1, byte2, byte3, byte4);
            }

            //五字节字符：U+00200000 – U+03FFFFFF	111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
            else if (code >= 0x00200000 && code <= 0x03FFFFFF) {
                byte1 =0xF0 | ((code>>24) & 0x03);
                byte2 =0xF0 | ((code>>18) & 0x3F);
                byte3 =0x80 | ((code>>12) & 0x3F);
                byte4 =0x80 | ((code>>6) & 0x3F);
                byte5 =0x80 | (code & 0x3F);
                res.push(byte1, byte2, byte3, byte4, byte5);
            }

            //六字节字符：U+04000000 – U+7FFFFFFF	1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
            else if (code >= 0x04000000 && code <= 0x7FFFFFFF) {
                byte1 =0xF0 | ((code>>30) & 0x01);
                byte2 =0xF0 | ((code>>24) & 0x3F);
                byte3 =0xF0 | ((code>>18) & 0x3F);
                byte4 =0x80 | ((code>>12) & 0x3F);
                byte5 =0x80 | ((code>>6) & 0x3F);
                byte6 =0x80 | (code & 0x3F);
                res.push(byte1, byte2, byte3, byte4, byte5, byte6);
            }
        }
        return res;
    },
    encode: function(str) {
        /**
         * 将任意字符串用Base64加密
         * str：要加密的字符串
         * utf8编码格式
         */
        if (!str) return '';
        var bytes = this.encUTF8(str);
        var i = 0, len = bytes.length, res = [];
        var c1, c2, c3;
        while (i < len) {
            c1 = bytes[i++] & 0xFF;
            res.push(Base64.encTable[c1 >> 2]);
            //结尾剩一个字节补2个=
            if (i == len) {
                res.push(Base64.encTable[(c1 & 0x03) << 4], '==');
                break;
            }

            c2 = bytes[i++];
            //结尾剩两个字节补1个=
            if (i == len) {
                res.push(Base64.encTable[((c1 & 0x03) << 4) | ((c2 >> 4) & 0x0F)]);
                res.push(Base64.encTable[(c2 & 0x0F) << 2], '=');
                break;
            }

            c3 = bytes[i++];
            res.push(Base64.encTable[((c1 & 0x3) << 4) | ((c2 >> 4) & 0x0F)]);
            res.push(Base64.encTable[((c2 & 0x0F) << 2) | ((c3 & 0xC0) >> 6)]);
            res.push(Base64.encTable[c3 & 0x3F]);
        }
        return res.join('');
    }
};

const chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode  */
const b64pad  = "="; /* base-64 pad character. "=" for strict RFC compliance   */

/*
 * //使用hmac_sha1算法计算字符串的签名
 *  return base-64 encoded strings
 */
let b64_hmac_sha1 = function(key, data){
    return binb2b64(core_hmac_sha1(key, data));
}

/*
 * Calculate the HMAC-SHA1 of a key and some data
 */
let core_hmac_sha1 = function(key, data){
    var bkey = str2binb(key);
    if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);

    var ipad = Array(16), opad = Array(16);
    for(var i = 0; i < 16; i++)
    {
        ipad[i] = bkey[i] ^ 0x36363636;
        opad[i] = bkey[i] ^ 0x5C5C5C5C;
    }

    var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
    return core_sha1(opad.concat(hash), 512 + 160);
}

/*
 * Convert an array of big-endian words to a base-64 string
 */
let binb2b64 = function(binarray){
    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var str = "";
    for(var i = 0; i < binarray.length * 4; i += 3)
    {
        var triplet = (((binarray[i   >> 2] >> 8 * (3 -  i   %4)) & 0xFF) << 16)
            | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
            |  ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
        for(var j = 0; j < 4; j++)
        {
            if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
            else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
        }
    }
    return str;
}

/*
 * Convert an 8-bit or 16-bit string to an array of big-endian words
 * In 8-bit function, characters >255 have their hi-byte silently ignored.
 */
let str2binb = function(str){
    var bin = Array();
    var mask = (1 << chrsz) - 1;
    for(var i = 0; i < str.length * chrsz; i += chrsz)
        bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
    return bin;
}

/*
 * Calculate the SHA-1 of an array of big-endian words, and a bit length
 */
let core_sha1 = function(x, len){
    /* append padding */
    x[len >> 5] |= 0x80 << (24 - len % 32);
    x[((len + 64 >> 9) << 4) + 15] = len;

    var w = Array(80);
    var a =  1732584193;
    var b = -271733879;
    var c = -1732584194;
    var d =  271733878;
    var e = -1009589776;

    for(var i = 0; i < x.length; i += 16)
    {
        var olda = a;
        var oldb = b;
        var oldc = c;
        var oldd = d;
        var olde = e;

        for(var j = 0; j < 80; j++)
        {
            if(j < 16) w[j] = x[i + j];
            else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
            var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
                safe_add(safe_add(e, w[j]), sha1_kt(j)));
            e = d;
            d = c;
            c = rol(b, 30);
            b = a;
            a = t;
        }

        a = safe_add(a, olda);
        b = safe_add(b, oldb);
        c = safe_add(c, oldc);
        d = safe_add(d, oldd);
        e = safe_add(e, olde);
    }
    return Array(a, b, c, d, e);

}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
let safe_add = function(x, y){
    var lsw = (x & 0xFFFF) + (y & 0xFFFF);
    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
    return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
let rol = function(num, cnt){
    return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Perform the appropriate triplet combination function for the current
 * iteration
 */
let sha1_ft = function(t, b, c, d){
    if(t < 20) return (b & c) | ((~b) & d);
    if(t < 40) return b ^ c ^ d;
    if(t < 60) return (b & c) | (b & d) | (c & d);
    return b ^ c ^ d;
}

/*
 * Determine the appropriate additive constant for the current iteration
 */
let sha1_kt = function(t){
    return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
        (t < 60) ? -1894007588 : -899497514;
}

var uuid = function () {
    var S4 = function () {
        return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    };
    return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
};

/**
 *  产生headers
 *  CanonicalizedKssHeaders
 */
let generateHeaders =function(header) {
    var str = '';
    var arr = [];
    if(header){
        var prefix = 'x-kss';
        let sortArr = [];
        for (const key in header) {
            if(key.indexOf(prefix) == 0){
                sortArr.push({
                    key,
                    value: header[key]
                });
            }
        };
        // step2 : 根据属性名排序
        sortArr.sort(function(a, b) { 
            return (a.key > b.key ? 1 : -1); 
        });
        
        // step3 : 拼接起来
        sortArr.forEach((element, index) => {
            if(index === sortArr.length - 1){
                str += element.key + ':' + element.value;
            }else{
                str += element.key + ':' + element.value + '\n';
            }
        });
    }
    return str;
}

/**
 * 根据SK和请求生成Signature（用于Authorization头部）
 * @param sk      secrete key
 * @param bucket  bucket name
 * @param resource  ObjectKey[?subResource]
 * @param httpVerb  PUT/GET/POST/DELETE
 * @param headers  headers of request
 * @param time_stamp  expiration time
 * @returns {*}
 */
let getAuth = function (sk, bucket, key, resource, httpVerb, headers, time_stamp){
    var Resource = encodeKey(key) + resource;
    var authHeaders = key2Lower(headers);
    var canonicalizedKssHeaders = generateHeaders(authHeaders);
    var canonicalizedResource = '/' + bucket + '/' + Resource;
    var contentType = authHeaders['content-type'] || '';
    var contentMD5 = authHeaders['content-md5'] || '';
    if (canonicalizedKssHeaders !== '') {
        var string2Sign = httpVerb.toUpperCase() + '\n' + contentMD5 + '\n' + contentType + '\n'  + time_stamp + '\n' + canonicalizedKssHeaders + '\n' + canonicalizedResource;
    } else {
        var string2Sign = httpVerb.toUpperCase() + '\n' + contentMD5 + '\n' + contentType + '\n' + time_stamp + '\n' + canonicalizedResource;
    }
    console.log('string2Sign:' + string2Sign);
    var signature = b64_hmac_sha1(sk, string2Sign);
    console.log('signature:' + signature);
    return signature;
}

function each(obj, fn) {
    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
            fn(obj[i], i);
        }
    }
}

//将header内的key都转成小写
function key2Lower(obj) {
    var headers = {};
	for (var key in obj) {
        headers[key.toLowerCase()] = obj[key]
	}
	return headers;
}

// 获取文件分片
function fileSlice(file, start, end, callback) {
    var blob;
    if (file.slice) {
        blob = file.slice(start, end);
    } else if (file.mozSlice) {
        blob = file.mozSlice(start, end);
    } else if (file.webkitSlice) {
        blob = file.webkitSlice(start, end);
    }

    var reader = new FileReader();
    reader.onload = function(e) {
        callback(e.target.result);
    };
    reader.readAsArrayBuffer(blob);
};

// 获取文件分片 新
var fileSliceForUpload = function (file, start, end, isUseToUpload, callback) {
    var blob;
    if (file.slice) { // W3C
        blob = file.slice(start, end);
    } else if (file.mozSlice) { // 火狐
        blob = file.mozSlice(start, end);
    } else if (file.webkitSlice) { // chrome
        blob = file.webkitSlice(start, end);
    }
    // if (isUseToUpload && fileSliceNeedCopy) {
    if (isUseToUpload) {
        var reader = new FileReader();
        reader.onload = function (e) {
            blob = null;
            callback(new Blob([reader.result]));
        };
        reader.readAsArrayBuffer(blob);
    } else {
        callback(blob);
    }
};

var readAsBinaryString = function (blob, callback) {
    var readFun;
    var fr = new FileReader();
    if (FileReader.prototype.readAsBinaryString) {
        readFun = FileReader.prototype.readAsBinaryString;
        fr.onload = function () {
            callback(this.result);
        };
    } else if (FileReader.prototype.readAsArrayBuffer) {
        // 在 ie11 添加 readAsBinaryString 兼容
        readFun = function (fileData) {
            var binary = '';
            var pt = this;
            var reader = new FileReader();
            reader.onload = function (e) {
                var bytes = new Uint8Array(reader.result);
                var length = bytes.byteLength;
                for (var i = 0; i < length; i++) {
                    binary += String.fromCharCode(bytes[i]);
                }
                callback(binary);
            };
            reader.readAsArrayBuffer(fileData);
        };
    } else {
        console.error('FileReader not support readAsBinaryString');
    }
    readFun.call(fr, blob);
};

// 获取文件 md5 值
var md5ChunkSize = 1024 * 1024;
var getFileMd5 = function (blob, callback, onProgress) {
    var size = blob.size;
    var loaded = 0;
    var md5ctx = md5.getCtx();
    var next = function (start) {
        if (start >= size) {
            var hash = md5ctx.digest('hex');
            callback(null, hash);
            return;
        }
        var end = Math.min(size, start + md5ChunkSize);
        fileSliceForUpload(blob, start, end,  false,function (chunk) {
            readAsBinaryString(chunk, function (content) {
                chunk = null;
                md5ctx = md5ctx.update(content, true);
                loaded += content.length;
                content = null;
                if (onProgress)
                    onProgress({ loaded: loaded, total: size, percent: Math.round((loaded / size) * 10000) / 10000 });
                next(start + md5ChunkSize);
            });
        });
    };
    next(0);
};

/**
 * 生成合并分块上传使用的xml
 */
 function generateCompleteXML(partList) {
    var length = partList.length;
    var str = '';
    if (length > 0) {
        str = '<CompleteMultipartUpload>';
        for (var i = 0; i < length; i++) {
            str += '<Part><PartNumber>' + partList[i].partNumber + '</PartNumber><ETag>' + partList[i].etag + '</ETag></Part>'
        }
        str += '</CompleteMultipartUpload>';
    }
    return str;
}

// 函数节流 - 进度
var throttleOnProgress = function (total, onProgress) {
    var self = this;
    var size0 = 0;
    var size1 = 0;
    var time0 = Date.now();
    var time1;
    var timer;

    function update() {
        timer = 0;
        if (onProgress && (typeof onProgress === 'function')) {
            time1 = Date.now();
            var speed = Math.max(0, Math.round((size1 - size0) / ((time1 - time0) / 1000) * 100) / 100) || 0;
            var percent;
            if (size1 === 0 && total === 0) {
                percent = 1;
            } else {
                percent = Math.floor(size1 / total * 100) / 100 || 0;
            }
            time0 = time1;
            size0 = size1;
            try {
                onProgress({loaded: size1, total: total, speed: speed, percent: percent});
            } catch (e) {
            }
        }
    }

    return function (info, immediately) {
        if (info) {
            size1 = info.loaded;
            total = info.total;
        }
        if (immediately) {
            clearTimeout(timer);
            update();
        } else {
            if (timer) return;
            timer = setTimeout(update, self.config.progressInterval);
        }
    };
};

// 控制并发数量
/**
 * arr Array  遍历集合
 * limit number 每次异步操作时允许的并发执行的任务数量
 * iteratee AsyncFunction 应用于遍历arr的异步函数，数组下标并不通过迭代器，如 果你需要用到index，使用eachOfLimit,调用 （item，callback）
 * callback function<optional> 当所有迭代器结束或者出现一个错误调用(err)
*/
var syncLimit = function (arr, limit, iterator, callback) {
    callback = callback || function () {};
    if (!arr.length || limit <= 0) {
        return callback();
    }

    var completed = 0;
    var started = 0;
    var running = 0;

    (function replenish () {
        if (completed >= arr.length) {
            return callback();
        }

        while (running < limit && started < arr.length) {
            started += 1;
            running += 1;
            iterator(arr[started - 1], function (err) {
                if (err) {
                    callback(err);
                    callback = function () {};
                } else {
                    completed += 1;
                    running -= 1;
                    if (completed >= arr.length) {
                        callback();
                    } else {
                        replenish();
                    }
                }
            });
        }
    })();
};


/**
 * 获取post方式签名
 * @param {*} sk 
 * @param {*} policy 
 * @returns 
 */
var getPostSignature = function (sk, policy) {
    var stringToSign = Base64.encode(JSON.stringify(policy));
	var signature = b64_hmac_sha1(sk, stringToSign);
	return signature;
}

/**
 * 获取字符串的base64结果
 * @param {*} policy 
 * @returns 
 */
var getStrBase64 = function (policy) {
    return Base64.encode(JSON.stringify(policy))
}


var getSourceParams = function (source) {
    var m = source.match(/\/([^\s]+?)\/(.+)$/im);
    if (!m) return null;
    return { Bucket: m[1], Key: m[2] };
}

var generatePresignedUrl = function(params, sk, ak, url){
    var StringToSign = '';
    var expiresTime = parseInt(new Date().getTime() / 1000) + params.expiration;
    var type = '';
    var MD5 = '';
    if(params.headers && params.headers['Content-Type']){
        type = params.headers['Content-Type'];
    }
    if(params.headers && params.headers['Content-MD5']){
        MD5 = params.headers['Content-MD5'];
    }
    var canonicalizedResource = '/' + params.bucket + '/' + params.key;
    //生成url
    var StringToSign = `${params.method}\n${MD5}\n${type}\n${expiresTime}\n${canonicalizedResource}`;
    console.log(StringToSign)
    var sign = b64_hmac_sha1(sk, StringToSign);
    if(url){
        return `${url}?KSSAccessKeyId=${ak}&Expires=${expiresTime}&Signature=${encodeURIComponent(sign)}`;
    }
    return sign;
}

var getFileSize = function (api, params, callback) {
    var size;
    if (typeof params.body === 'string') { // 如果是字符串，转化为 Blob 对象
        params.body = new Blob([params.body], { type: 'text/plain' });
    } else if (params.file instanceof ArrayBuffer) {
        params.body = new Blob([params.body]);
    }
    // 保证 Body 存在，并且属于 Blob 对象或 File 对象
    if (
        params.body &&
        (params.body instanceof Blob ||
            params.body.toString() === '[object File]' ||
            params.body.toString() === '[object Blob]')
    ) {
        size = params.body.size;
    } else {
        callback(utils.error(new Error('params body format error, Only allow File|Blob|String.')));
        return;
    }
    params.contentLength = size;
    callback(null, size);
};

function isArray(arr) {
    return arr instanceof Array;
}

function map(obj, fn) {
    var o = isArray(obj) ? [] : {};
    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
            o[i] = fn(obj[i], i);
        }
    }
    return o;
}

function clone(obj) {
    return map(obj, function (v) {
        return typeof v === 'object' && v !== null ? clone(v) : v;
    });
}

function extend(target, source) {
    each(source, function (val, key) {
        target[key] = source[key];
    });
    return target;
}

var error = function (err, opt) {
    var sourceErr = err;
    err.message = err.message || null;

    if (typeof opt === 'string') {
        err.error = opt;
        err.message = opt;
    } else if (typeof opt === 'object' && opt !== null) {
        extend(err, opt);
        if (opt.code || opt.name) err.code = opt.code || opt.name;
        if (opt.message) err.message = opt.message;
        if (opt.stack) err.stack = opt.stack;
    }

    if (typeof Object.defineProperty === 'function') {
        Object.defineProperty(err, 'name', { writable: true, enumerable: false });
        Object.defineProperty(err, 'message', { enumerable: true });
    }

    err.name = (opt && opt.name) || err.name || err.code || 'Error';
    if (!err.code) err.code = err.name;
    if (!err.error) err.error = clone(sourceErr); // 兼容老的错误格式

    return err;
};

function filter(obj, fn) {
    var iaArr = isArray(obj);
    var o = iaArr ? [] : {};
    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
            if (fn(obj[i], i)) {
                if (iaArr) {
                    o.push(obj[i]);
                } else {
                    o[i] = obj[i];
                }
            }
        }
    }
    return o;
}

function isInArray(arr, item) {
    var flag = false;
    for (var i = 0; i < arr.length; i++) {
        if (item === arr[i]) {
            flag = true;
            break;
        }
    }
    return flag;
}

// 获取文件内容的 MD5
var getBodyMd5 = function (UploadCheckContentMd5, Body, callback) {
    if (UploadCheckContentMd5) {
        if (typeof Body === 'string') {
            callback(utils.md5(Body, true));
        } else if (Blob && Body instanceof Blob) {
            utils.getFileMd5(
                Body,
                function (err, md5) {
                    callback(md5);
                },
                // onProgress
            );
        } else {
            callback();
        }
    } else {
        callback();
    }
};

var getBodyMd5Promise = function (uploadCheckContentMd5, Body) {
    return new Promise((resolve, reject) => {
        if (!uploadCheckContentMd5) {
            resolve();
            return;
        }

        if (typeof Body === 'string') {
            resolve(utils.md5(Body, true));
            return;
        }

        if (Blob && Body instanceof Blob) {
            utils.getFileMd5(
                Body,
                (err, md5) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(md5);
                    }
                },
                // onProgress // 如果有进度回调，保持不变
            );
        } else {
            resolve(); // 对于未知类型的 Body，直接 resolve()
        }
    });
};

var b64 = function (str) {
    var i,
        len,
        char,
        res = '';
    for (i = 0, len = str.length / 2; i < len; i++) {
        char = parseInt(str[i * 2] + str[i * 2 + 1], 16);
        res += String.fromCharCode(char);
    }
    return btoa(res);
};


var utils = {
    addURLParam,
    b64_hmac_sha1,
    getAuth,
    getPostSignature,
    getStrBase64,
    each,
    isInArray,
    filter,
    fileSlice,
    fileSliceForUpload,
    md5,
    getFileMd5,
    generateCompleteXML,
    throttleOnProgress,
    syncLimit,
    getSourceParams,
    encodeKey,
    generatePresignedUrl,
    ENDPOINT,
    uuid,
    b64,
    getFileSize,
    map,
    error,
    extend,
    getBodyMd5,
    getBodyMd5Promise
};

module.exports = utils;
